#pragma once

#include <string.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>

#include "Inode.h"
#include "FCB.h"
#include "CurrentStatus.h"
#include "Block.h"
#include "Constants.h"
#include "../util/Tools.h"
#include "../util/Command.h"
#include "../util/CreateFileParams.h"
#include "../util/FileIO.h"
#include "../util/PathTracer.h"

typedef struct FileSystem {
    char blockBitmap[2 * BLOCK_SIZE];
    FCB FCBList[FCB_LIST_SIZE];
    Inode InodeList[INODE_LIST_SIZE];
    char data[DATA_SIZE];
} FileSystem;

static void showWelcome() {
    EXIT_IF_TRUE(sizeof(FileSystem) != TOTAL_SIZE, "Your OS is not supported by this file system")
    puts("---------------------------------------------");
    printf("Size of file system is 0x%lx bytes\n", sizeof(FileSystem));
    putchar('\n');
    puts("Contributor:");
    puts("     杨浩斌 202030430240");
    puts("     李却非 202066191191");
    puts("     杨子艺 202030430370");
    putchar('\n');
    puts("Login success");
    putchar('\n');
    puts("---------------------------------------------");
    putchar('\n');
}

// 初始化文件系统，包括检查系统的兼容性、加载磁盘上的数据、初始化文件系统等
static FileSystem *FileSystem_init() {
    showWelcome();

    FileSystem *fs = (FileSystem *) calloc(TOTAL_SIZE, sizeof(char));
    int load = loadFromDisk(fs);
    if (!load) {
        puts("File has been destroy! create new file system");
        InodeList_createInode(fs->InodeList, 0, NOTHING, True);
        FCBList_createFCB(fs->FCBList, "/", 0);
    }
    return fs;
}

// 释放内存并保存至磁盘
static void FileSystem_releaseAndPersist(FileSystem **fs) {
    Status status = saveToDisk(*fs);
    EXIT_IF_TRUE(status == Error, "Fatal error!")
    FREE_AND_SET_NULL(*fs)
}

// 填充文件内容
static Status PRI_fillContent(FileSystem *fs, int inodePtr, int fileSize) {
    Inode *inode = fs->InodeList + inodePtr;

    for (int i = 0; i < Min(DIRECT_BLOCK_SIZE, fileSize); ++i) {
        short blockPtr = Bitmap_distributeBlock(fs->blockBitmap);
        RETURN_IF_TRUE(blockPtr == NOTHING, Error)
        inode->directBlocks[i] = blockPtr;
        Block_fillBlock(fs->data + inode->directBlocks[i] * BLOCK_SIZE);
    }
    if (fileSize > DIRECT_BLOCK_SIZE) {
        inode->indirectBlock = Bitmap_distributeBlock(fs->blockBitmap);
        short *indirectBlocks = (short *) (fs->data + inode->indirectBlock * BLOCK_SIZE);
        for (int i = 0; i < fileSize - DIRECT_BLOCK_SIZE; ++i) {
            indirectBlocks[i] = Bitmap_distributeBlock(fs->blockBitmap);
            Block_fillBlock(fs->data + indirectBlocks[i] * BLOCK_SIZE);
        }
    }
    return Success;
}

// 创建文件
static CreateFileResult *FileSystem_createFile(FileSystem *fs, CreateFileParams *params, CurrentStatus *currentStatus) {
    char *fileName = params->pathInfo->path[--params->pathInfo->pathSize]; // 最后一个不是目录
    RETURN_IF_TRUE(params->fileSize > MAX_FILE_SIZE / BLOCK_SIZE
                    || params->fileSize < 0
                    || strlen(fileName) >= MAX_FILE_NAME_LENGTH,
                   CreateFileResult_init(Error, NOTHING))
    int workDirPtr = Path_getOrCreateWorkDir(fs->FCBList, fs->InodeList, params->pathInfo,
                                             currentStatus->currentDirectory, True);
    RETURN_IF_TRUE(workDirPtr == NOTHING, CreateFileResult_init(Error, NOTHING))
    RETURN_IF_TRUE(Path_findFileOrDir(fs->FCBList, fs->InodeList, workDirPtr, fileName) != NOTHING,
                   CreateFileResult_init(Error, NOTHING))
    short inodePtr = InodeList_createInode(fs->InodeList, params->fileSize, workDirPtr, False);
    RETURN_IF_TRUE(PRI_fillContent(fs, inodePtr, params->fileSize) == Error, CreateFileResult_init(Error, NOTHING))
    short fileFCBPtr = FCBList_createFCB(fs->FCBList, fileName, inodePtr);
    Inode *currentInode = fs->InodeList + fs->FCBList[workDirPtr].inodeNumber;
    Inode_addChild(fs->FCBList, fs->InodeList, currentInode, inodePtr);
    CreateFileResult *result = CreateFileResult_init(Success, fileFCBPtr);
    return result;
}

// 创建目录
static Status FileSystem_createDir(FileSystem *fs, PathInfo *pathInfo, CurrentStatus *currentStatus) {
    int finalDir = Path_getOrCreateWorkDir(fs->FCBList, fs->InodeList, pathInfo,
                                           currentStatus->currentDirectory, True);
    RETURN_IF_TRUE(finalDir == NOTHING, Error)
    return Success;
}

// 删除文件
static void PRI_deleteFile(FileSystem *fs, FCB *fileFcb, Inode *fileInode) {
    fileFcb->name[0] = '\0'; // 逻辑删除FCB
    fileInode->status = 0; // 逻辑删除Inode
    // 删除文件所有block
    short *indirectBlocks = (short *) (fs->data + fileInode->indirectBlock * BLOCK_SIZE);
    for (int i = 0; i < fileInode->fileSize; ++i) {
        if (i < DIRECT_BLOCK_SIZE) {
            Bitmap_removeBlock(fs->blockBitmap, fileInode->directBlocks[i]);
        } else {
            Bitmap_removeBlock(fs->blockBitmap, indirectBlocks[i - DIRECT_BLOCK_SIZE]);
        }
    }
    if (fileInode->fileSize > DIRECT_BLOCK_SIZE) {
        Bitmap_removeBlock(fs->blockBitmap, fileInode->indirectBlock);
    }
}

// 删除文件，并从当前目录中删除子项
static Status FileSystem_deleteFile(FileSystem *fs, PathInfo *pathInfo, CurrentStatus *currentStatus) {
    RETURN_IF_TRUE(pathInfo->pathSize == 0, Error)
    char *fileName = pathInfo->path[--pathInfo->pathSize];
    int workDirPtr = Path_getOrCreateWorkDir(fs->FCBList, fs->InodeList, pathInfo,
                                             currentStatus->currentDirectory, False);
    RETURN_IF_TRUE(workDirPtr == NOTHING, Error)
    FCB *workdirFCB = fs->FCBList + workDirPtr;
    Inode *workdirInode = fs->InodeList + workdirFCB->inodeNumber;
    short fileFCBPtr = Path_findFileOrDir(fs->FCBList, fs->InodeList, workDirPtr, fileName);
    RETURN_IF_TRUE(fileFCBPtr == NOTHING, Error)
    FCB *fileFcb = fs->FCBList + fileFCBPtr;
    Inode *fileInode = fs->InodeList + fileFcb->inodeNumber;
    RETURN_IF_TRUE(fileInode->indirectBlock < 0, Error) // 如果是文件夹
    PRI_deleteFile(fs, fileFcb, fileInode);
    Inode_deleteChild(fs->FCBList, fs->InodeList, workdirInode, fileFCBPtr);
    return Success;
}

// 递归删除目录
static void PRI_dfsDeleteDir(FileSystem *fs, FCB *dirFCB) {
    Inode *dirInode = fs->InodeList + dirFCB->inodeNumber;
    // 逻辑删除FCB和Inode
    dirFCB->name[0] = '\0';
    dirInode->status = 0;
    if (dirInode->leftmostChild == NOTHING) {
        return;
    }
    FCB *childFCB = fs->FCBList + dirInode->leftmostChild;
    Inode *childInode = fs->InodeList + childFCB->inodeNumber;
    while (True) {
        if (childInode->indirectBlock < 0) {
            PRI_dfsDeleteDir(fs, childFCB);
        } else {
            PRI_deleteFile(fs, childFCB, childInode);
        }
        if (childInode->rightSibling == NOTHING) {
            break;
        }
        childFCB = fs->FCBList + childInode->rightSibling;
        childInode = fs->InodeList + childFCB->inodeNumber;
    }
}

// 删除目录，并从当前目录中删除子项
static Status FileSystem_deleteDir(FileSystem *fs, CurrentStatus *currentStatus, PathInfo *pathInfo) {
    RETURN_IF_TRUE(pathInfo->pathSize == 0, Error)
    char *fileName = pathInfo->path[--pathInfo->pathSize];
    short workDirPtr = Path_getOrCreateWorkDir(fs->FCBList, fs->InodeList, pathInfo,
                                             currentStatus->currentDirectory, False);
    RETURN_IF_TRUE(workDirPtr == NOTHING, Error)

    FCB *workDirFCB = fs->FCBList + workDirPtr;
    Inode *workDirInode = fs->InodeList + workDirFCB->inodeNumber;
    short dirFCBPtr = Path_findFileOrDir(fs->FCBList, fs->InodeList, workDirPtr, fileName);
    RETURN_IF_TRUE(dirFCBPtr == NOTHING, Error)
    RETURN_IF_TRUE(Path_isParent(fs->FCBList, fs->InodeList, dirFCBPtr, currentStatus->currentDirectory), Error)
    PRI_dfsDeleteDir(fs, fs->FCBList + dirFCBPtr);
    Inode_deleteChild(fs->FCBList, fs->InodeList, workDirInode, dirFCBPtr);
    return Success;
}

// 输出文件内容
static Status FileSystem_cat(FileSystem *fs, PathInfo *pathInfo, CurrentStatus *currentStatus) {
    char *fileName = pathInfo->path[--pathInfo->pathSize];
    int workDirPtr = Path_getOrCreateWorkDir(fs->FCBList, fs->InodeList, pathInfo,
                                             currentStatus->currentDirectory, False);
    RETURN_IF_TRUE(workDirPtr == NOTHING, Error)
    int fileFCBPtr = Path_findFileOrDir(fs->FCBList, fs->InodeList, workDirPtr, fileName);
    Inode *fileInode = fs->InodeList + fs->FCBList[fileFCBPtr].inodeNumber;
    Block_printContent(fs->data, fileInode);
    return Success;
}

// 显示当前目录所有文件和文件夹
static void FileSystem_dir(FileSystem *fs, CurrentStatus *currentStatus) {
    FCB *dirFCB = fs->FCBList + currentStatus->currentDirectory;
    Inode *dirInode = fs->InodeList + dirFCB->inodeNumber;
    if (dirInode->leftmostChild == NOTHING) {
        puts("(Empty)");
        return;
    }
    FCB *currentFCB = fs->FCBList + dirInode->leftmostChild;
    Inode *currentInode = fs->InodeList + currentFCB->inodeNumber;
    while (True) {
        if (currentInode->indirectBlock < 0) {
            BROWN
        }
        struct tm *createTime;
        createTime = localtime(&currentInode->createTime);
        if (currentInode->indirectBlock < 0) {
            printf("%-8s %-12s    \t %02d-%02d-%02d %02d:%02d:%02d\n", "Dir",
                    currentFCB->name,
                    createTime->tm_year + 1900, createTime->tm_mon + 1, createTime->tm_mday,
                    createTime->tm_hour, createTime->tm_min, createTime->tm_sec);
        } else {
            printf("%-8s %-12s %dKB\t %02d-%02d-%02d %02d:%02d:%02d\n", "File",
                    currentFCB->name, currentInode->fileSize,
                    createTime->tm_year + 1900, createTime->tm_mon + 1, createTime->tm_mday,
                    createTime->tm_hour, createTime->tm_min, createTime->tm_sec);
        }

        COLOR_RESET
        if (currentInode->rightSibling == NOTHING) {
            break;
        }
        currentFCB = fs->FCBList + currentInode->rightSibling;
        currentInode = fs->InodeList + currentFCB->inodeNumber;
    }
    putchar('\n');
}

// 切换目录
static Status FileSystem_changeDir(FileSystem *fs, CurrentStatus *currentStatus, PathInfo *pathInfo) {
    short finalDir = Path_getOrCreateWorkDir(fs->FCBList, fs->InodeList, pathInfo,
                                             currentStatus->currentDirectory, False);
    RETURN_IF_TRUE(finalDir == NOTHING, Error)
    currentStatus->currentDirectory = finalDir;
    return Success;
}

// 复制文件
static Status FileSystem_copyFile(FileSystem *fs, PathInfo *srcPathInfo,
                                  PathInfo *destPathInfo, CurrentStatus *currentStatus) {
    char *srcFileName = srcPathInfo->path[--srcPathInfo->pathSize];
    int srcDirPtr = Path_getOrCreateWorkDir(fs->FCBList, fs->InodeList, srcPathInfo,
                                            currentStatus->currentDirectory, False);
    RETURN_IF_TRUE(srcDirPtr == NOTHING, Error)
    int srcFileFCBPtr = Path_findFileOrDir(fs->FCBList, fs->InodeList, srcDirPtr, srcFileName);
    RETURN_IF_TRUE(srcFileFCBPtr == NOTHING, Error)
    FCB *srcFileFCB = fs->FCBList + srcFileFCBPtr;
    Inode *srcFileInode = fs->InodeList + srcFileFCB->inodeNumber;
    RETURN_IF_TRUE(srcFileInode->indirectBlock < 0, Error) // 文件夹不能复制

    CreateFileParams *createFileParams = CreateFileParams_init2(destPathInfo, srcFileInode->fileSize);
    CreateFileResult *result = FileSystem_createFile(fs, createFileParams, currentStatus);
    RETURN_IF_TRUE(result->status == Error, Error);
    int destFileFCBPtr = result->fileFCBPtr;
    FCB *destFileFCB = fs->FCBList + destFileFCBPtr;
    Inode *destFileInode = fs->InodeList + destFileFCB->inodeNumber;
    Block_copyContent(fs->data, srcFileInode, destFileInode);

    createFileParams->pathInfo = NULL;
    CreateFileParams_release(&createFileParams);
    FREE_AND_SET_NULL(result)
    return Success;
}

// 显示磁盘占用
static void FileSystem_spaceUsage(FileSystem *fs) {
    int used = Block_getUsed(fs->blockBitmap) + DATA_START;
    printf("used:%d\t unused:%d\n", used, NUM_BLOCKS - used);
}